/*
 * Copyright (c) 2007, 2015, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */
/*
 * Copyright 2001-2004 The Apache Software Foundation.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/*
 * $Id: Parser.java,v 1.2.4.1 2005/09/13 12:14:32 pvedula Exp $
 */

package com.sun.org.apache.xalan.internal.xsltc.compiler;

import com.sun.java_cup.internal.runtime.Symbol;
import com.sun.org.apache.xalan.internal.XalanConstants;
import com.sun.org.apache.xalan.internal.utils.FactoryImpl;
import com.sun.org.apache.xalan.internal.utils.ObjectFactory;
import com.sun.org.apache.xalan.internal.utils.SecuritySupport;
import com.sun.org.apache.xalan.internal.utils.XMLSecurityManager;
import com.sun.org.apache.xalan.internal.xsltc.compiler.util.ErrorMsg;
import com.sun.org.apache.xalan.internal.xsltc.compiler.util.MethodType;
import com.sun.org.apache.xalan.internal.xsltc.compiler.util.Type;
import com.sun.org.apache.xalan.internal.xsltc.compiler.util.TypeCheckError;
import com.sun.org.apache.xml.internal.serializer.utils.SystemIDResolver;
import java.io.File;
import java.io.IOException;
import java.io.StringReader;
import java.util.Dictionary;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Properties;
import java.util.Stack;
import java.util.StringTokenizer;
import java.util.Vector;
import javax.xml.XMLConstants;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.InputSource;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.SAXNotRecognizedException;
import org.xml.sax.SAXParseException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.AttributesImpl;

/**
 * @author Jacek Ambroziak
 * @author Santiago Pericas-Geertsen
 * @author G. Todd Miller
 * @author Morten Jorgensen
 * @author Erwin Bolwidt <ejb@klomp.org>
 */
public class Parser implements Constants, ContentHandler {

  private static final String XSL = "xsl";            // standard prefix
  private static final String TRANSLET = "translet"; // extension prefix

  private Locator _locator = null;

  private XSLTC _xsltc;             // Reference to the compiler object.
  private XPathParser _xpathParser; // Reference to the XPath parser.
  private Vector _errors;           // Contains all compilation errors
  private Vector _warnings;         // Contains all compilation errors

  private Hashtable _instructionClasses; // Maps instructions to classes
  private Hashtable _instructionAttrs;
  ;  // reqd and opt attrs
  private Hashtable _qNames;
  private Hashtable _namespaces;
  private QName _useAttributeSets;
  private QName _excludeResultPrefixes;
  private QName _extensionElementPrefixes;
  private Hashtable _variableScope;
  private Stylesheet _currentStylesheet;
  private SymbolTable _symbolTable; // Maps QNames to syntax-tree nodes
  private Output _output;
  private Template _template;    // Reference to the template being parsed.

  private boolean _rootNamespaceDef; // Used for validity check

  private SyntaxTreeNode _root;

  private String _target;

  private int _currentImportPrecedence;

  private boolean _useServicesMechanism = true;

  public Parser(XSLTC xsltc, boolean useServicesMechanism) {
    _xsltc = xsltc;
    _useServicesMechanism = useServicesMechanism;
  }

  public void init() {
    _qNames = new Hashtable(512);
    _namespaces = new Hashtable();
    _instructionClasses = new Hashtable();
    _instructionAttrs = new Hashtable();
    _variableScope = new Hashtable();
    _template = null;
    _errors = new Vector();
    _warnings = new Vector();
    _symbolTable = new SymbolTable();
    _xpathParser = new XPathParser(this);
    _currentStylesheet = null;
    _output = null;
    _root = null;
    _rootNamespaceDef = false;
    _currentImportPrecedence = 1;

    initStdClasses();
    initInstructionAttrs();
    initExtClasses();
    initSymbolTable();

    _useAttributeSets =
        getQName(XSLT_URI, XSL, "use-attribute-sets");
    _excludeResultPrefixes =
        getQName(XSLT_URI, XSL, "exclude-result-prefixes");
    _extensionElementPrefixes =
        getQName(XSLT_URI, XSL, "extension-element-prefixes");
  }

  public void setOutput(Output output) {
    if (_output != null) {
      if (_output.getImportPrecedence() <= output.getImportPrecedence()) {
        String cdata = _output.getCdata();
        output.mergeOutput(_output);
        _output.disable();
        _output = output;
      } else {
        output.disable();
      }
    } else {
      _output = output;
    }
  }

  public Output getOutput() {
    return _output;
  }

  public Properties getOutputProperties() {
    return getTopLevelStylesheet().getOutputProperties();
  }

  public void addVariable(Variable var) {
    addVariableOrParam(var);
  }

  public void addParameter(Param param) {
    addVariableOrParam(param);
  }

  private void addVariableOrParam(VariableBase var) {
    Object existing = _variableScope.get(var.getName());
    if (existing != null) {
      if (existing instanceof Stack) {
        Stack stack = (Stack) existing;
        stack.push(var);
      } else if (existing instanceof VariableBase) {
        Stack stack = new Stack();
        stack.push(existing);
        stack.push(var);
        _variableScope.put(var.getName(), stack);
      }
    } else {
      _variableScope.put(var.getName(), var);
    }
  }

  public void removeVariable(QName name) {
    Object existing = _variableScope.get(name);
    if (existing instanceof Stack) {
      Stack stack = (Stack) existing;
      if (!stack.isEmpty()) {
        stack.pop();
      }
      if (!stack.isEmpty()) {
        return;
      }
    }
    _variableScope.remove(name);
  }

  public VariableBase lookupVariable(QName name) {
    Object existing = _variableScope.get(name);
    if (existing instanceof VariableBase) {
      return ((VariableBase) existing);
    } else if (existing instanceof Stack) {
      Stack stack = (Stack) existing;
      return ((VariableBase) stack.peek());
    }
    return (null);
  }

  public void setXSLTC(XSLTC xsltc) {
    _xsltc = xsltc;
  }

  public XSLTC getXSLTC() {
    return _xsltc;
  }

  public int getCurrentImportPrecedence() {
    return _currentImportPrecedence;
  }

  public int getNextImportPrecedence() {
    return ++_currentImportPrecedence;
  }

  public void setCurrentStylesheet(Stylesheet stylesheet) {
    _currentStylesheet = stylesheet;
  }

  public Stylesheet getCurrentStylesheet() {
    return _currentStylesheet;
  }

  public Stylesheet getTopLevelStylesheet() {
    return _xsltc.getStylesheet();
  }

  public QName getQNameSafe(final String stringRep) {
    // parse and retrieve namespace
    final int colon = stringRep.lastIndexOf(':');
    if (colon != -1) {
      final String prefix = stringRep.substring(0, colon);
      final String localname = stringRep.substring(colon + 1);
      String namespace = null;

      // Get the namespace uri from the symbol table
      if (prefix.equals(XMLNS_PREFIX) == false) {
        namespace = _symbolTable.lookupNamespace(prefix);
        if (namespace == null) {
          namespace = EMPTYSTRING;
        }
      }
      return getQName(namespace, prefix, localname);
    } else {
      final String uri = stringRep.equals(XMLNS_PREFIX) ? null
          : _symbolTable.lookupNamespace(EMPTYSTRING);
      return getQName(uri, null, stringRep);
    }
  }

  public QName getQName(final String stringRep) {
    return getQName(stringRep, true, false);
  }

  public QName getQNameIgnoreDefaultNs(final String stringRep) {
    return getQName(stringRep, true, true);
  }

  public QName getQName(final String stringRep, boolean reportError) {
    return getQName(stringRep, reportError, false);
  }

  private QName getQName(final String stringRep, boolean reportError,
      boolean ignoreDefaultNs) {
    // parse and retrieve namespace
    final int colon = stringRep.lastIndexOf(':');
    if (colon != -1) {
      final String prefix = stringRep.substring(0, colon);
      final String localname = stringRep.substring(colon + 1);
      String namespace = null;

      // Get the namespace uri from the symbol table
      if (prefix.equals(XMLNS_PREFIX) == false) {
        namespace = _symbolTable.lookupNamespace(prefix);
        if (namespace == null && reportError) {
          final int line = getLineNumber();
          ErrorMsg err = new ErrorMsg(ErrorMsg.NAMESPACE_UNDEF_ERR,
              line, prefix);
          reportError(ERROR, err);
        }
      }
      return getQName(namespace, prefix, localname);
    } else {
      if (stringRep.equals(XMLNS_PREFIX)) {
        ignoreDefaultNs = true;
      }
      final String defURI = ignoreDefaultNs ? null
          : _symbolTable.lookupNamespace(EMPTYSTRING);
      return getQName(defURI, null, stringRep);
    }
  }

  public QName getQName(String namespace, String prefix, String localname) {
    if (namespace == null || namespace.equals(EMPTYSTRING)) {
      QName name = (QName) _qNames.get(localname);
      if (name == null) {
        name = new QName(null, prefix, localname);
        _qNames.put(localname, name);
      }
      return name;
    } else {
      Dictionary space = (Dictionary) _namespaces.get(namespace);
      String lexicalQName =
          (prefix == null || prefix.length() == 0)
              ? localname
              : (prefix + ':' + localname);

      if (space == null) {
        final QName name = new QName(namespace, prefix, localname);
        _namespaces.put(namespace, space = new Hashtable());
        space.put(lexicalQName, name);
        return name;
      } else {
        QName name = (QName) space.get(lexicalQName);
        if (name == null) {
          name = new QName(namespace, prefix, localname);
          space.put(lexicalQName, name);
        }
        return name;
      }
    }
  }

  public QName getQName(String scope, String name) {
    return getQName(scope + name);
  }

  public QName getQName(QName scope, QName name) {
    return getQName(scope.toString() + name.toString());
  }

  public QName getUseAttributeSets() {
    return _useAttributeSets;
  }

  public QName getExtensionElementPrefixes() {
    return _extensionElementPrefixes;
  }

  public QName getExcludeResultPrefixes() {
    return _excludeResultPrefixes;
  }

  /**
   * Create an instance of the <code>Stylesheet</code> class,
   * and then parse, typecheck and compile the instance.
   * Must be called after <code>parse()</code>.
   */
  public Stylesheet makeStylesheet(SyntaxTreeNode element)
      throws CompilerException {
    try {
      Stylesheet stylesheet;

      if (element instanceof Stylesheet) {
        stylesheet = (Stylesheet) element;
      } else {
        stylesheet = new Stylesheet();
        stylesheet.setSimplified();
        stylesheet.addElement(element);
        stylesheet.setAttributes((AttributesImpl) element.getAttributes());

        // Map the default NS if not already defined
        if (element.lookupNamespace(EMPTYSTRING) == null) {
          element.addPrefixMapping(EMPTYSTRING, EMPTYSTRING);
        }
      }
      stylesheet.setParser(this);
      return stylesheet;
    } catch (ClassCastException e) {
      ErrorMsg err = new ErrorMsg(ErrorMsg.NOT_STYLESHEET_ERR, element);
      throw new CompilerException(err.toString());
    }
  }

  /**
   * Instanciates a SAX2 parser and generate the AST from the input.
   */
  public void createAST(Stylesheet stylesheet) {
    try {
      if (stylesheet != null) {
        stylesheet.parseContents(this);
        final int precedence = stylesheet.getImportPrecedence();
        final Enumeration elements = stylesheet.elements();
        while (elements.hasMoreElements()) {
          Object child = elements.nextElement();
          if (child instanceof Text) {
            final int l = getLineNumber();
            ErrorMsg err =
                new ErrorMsg(ErrorMsg.ILLEGAL_TEXT_NODE_ERR, l, null);
            reportError(ERROR, err);
          }
        }
        if (!errorsFound()) {
          stylesheet.typeCheck(_symbolTable);
        }
      }
    } catch (TypeCheckError e) {
      reportError(ERROR, new ErrorMsg(ErrorMsg.JAXP_COMPILE_ERR, e));
    }
  }

  /**
   * Parses a stylesheet and builds the internal abstract syntax tree
   *
   * @param reader A SAX2 SAXReader (parser)
   * @param input A SAX2 InputSource can be passed to a SAX reader
   * @return The root of the abstract syntax tree
   */
  public SyntaxTreeNode parse(XMLReader reader, InputSource input) {
    try {
      // Parse the input document and build the abstract syntax tree
      reader.setContentHandler(this);
      reader.parse(input);
      // Find the start of the stylesheet within the tree
      return (SyntaxTreeNode) getStylesheet(_root);
    } catch (IOException e) {
      if (_xsltc.debug()) {
        e.printStackTrace();
      }
      reportError(ERROR, new ErrorMsg(ErrorMsg.JAXP_COMPILE_ERR, e));
    } catch (SAXException e) {
      Throwable ex = e.getException();
      if (_xsltc.debug()) {
        e.printStackTrace();
        if (ex != null) {
          ex.printStackTrace();
        }
      }
      reportError(ERROR, new ErrorMsg(ErrorMsg.JAXP_COMPILE_ERR, e));
    } catch (CompilerException e) {
      if (_xsltc.debug()) {
        e.printStackTrace();
      }
      reportError(ERROR, new ErrorMsg(ErrorMsg.JAXP_COMPILE_ERR, e));
    } catch (Exception e) {
      if (_xsltc.debug()) {
        e.printStackTrace();
      }
      reportError(ERROR, new ErrorMsg(ErrorMsg.JAXP_COMPILE_ERR, e));
    }
    return null;
  }

  /**
   * Parses a stylesheet and builds the internal abstract syntax tree
   *
   * @param input A SAX2 InputSource can be passed to a SAX reader
   * @return The root of the abstract syntax tree
   */
  public SyntaxTreeNode parse(InputSource input) {
    try {
      // Create a SAX parser and get the XMLReader object it uses
      final SAXParserFactory factory = FactoryImpl.getSAXFactory(_useServicesMechanism);

      if (_xsltc.isSecureProcessing()) {
        try {
          factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
        } catch (SAXException e) {
        }
      }

      try {
        factory.setFeature(Constants.NAMESPACE_FEATURE, true);
      } catch (Exception e) {
        factory.setNamespaceAware(true);
      }
      final SAXParser parser = factory.newSAXParser();
      try {
        parser.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD,
            _xsltc.getProperty(XMLConstants.ACCESS_EXTERNAL_DTD));
      } catch (SAXNotRecognizedException e) {
        ErrorMsg err = new ErrorMsg(ErrorMsg.WARNING_MSG,
            parser.getClass().getName() + ": " + e.getMessage());
        reportError(WARNING, err);
      }

      final XMLReader reader = parser.getXMLReader();
      try {
        XMLSecurityManager securityManager =
            (XMLSecurityManager) _xsltc.getProperty(XalanConstants.SECURITY_MANAGER);
        for (XMLSecurityManager.Limit limit : XMLSecurityManager.Limit.values()) {
          reader.setProperty(limit.apiProperty(), securityManager.getLimitValueAsString(limit));
        }
        if (securityManager.printEntityCountInfo()) {
          parser.setProperty(XalanConstants.JDK_ENTITY_COUNT_INFO, XalanConstants.JDK_YES);
        }
      } catch (SAXException se) {
        System.err.println("Warning:  " + reader.getClass().getName() + ": "
            + se.getMessage());
      }

      return (parse(reader, input));
    } catch (ParserConfigurationException e) {
      ErrorMsg err = new ErrorMsg(ErrorMsg.SAX_PARSER_CONFIG_ERR);
      reportError(ERROR, err);
    } catch (SAXParseException e) {
      reportError(ERROR, new ErrorMsg(e.getMessage(), e.getLineNumber()));
    } catch (SAXException e) {
      reportError(ERROR, new ErrorMsg(e.getMessage()));
    }
    return null;
  }

  public SyntaxTreeNode getDocumentRoot() {
    return _root;
  }

  private String _PImedia = null;
  private String _PItitle = null;
  private String _PIcharset = null;

  /**
   * Set the parameters to use to locate the correct <?xml-stylesheet ...?>
   * processing instruction in the case where the input document is an
   * XML document with one or more references to a stylesheet.
   *
   * @param media The media attribute to be matched. May be null, in which case the prefered
   * templates will be used (i.e. alternate = no).
   * @param title The value of the title attribute to match. May be null.
   * @param charset The value of the charset attribute to match. May be null.
   */
  protected void setPIParameters(String media, String title, String charset) {
    _PImedia = media;
    _PItitle = title;
    _PIcharset = charset;
  }

  /**
   * Extracts the DOM for the stylesheet. In the case of an embedded
   * stylesheet, it extracts the DOM subtree corresponding to the
   * embedded stylesheet that has an 'id' attribute whose value is the
   * same as the value declared in the <?xml-stylesheet...?> processing
   * instruction (P.I.). In the xml-stylesheet P.I. the value is labeled
   * as the 'href' data of the P.I. The extracted DOM representing the
   * stylesheet is returned as an Element object.
   */
  private SyntaxTreeNode getStylesheet(SyntaxTreeNode root)
      throws CompilerException {

    // Assume that this is a pure XSL stylesheet if there is not
    // <?xml-stylesheet ....?> processing instruction
    if (_target == null) {
      if (!_rootNamespaceDef) {
        ErrorMsg msg = new ErrorMsg(ErrorMsg.MISSING_XSLT_URI_ERR);
        throw new CompilerException(msg.toString());
      }
      return (root);
    }

    // Find the xsl:stylesheet or xsl:transform with this reference
    if (_target.charAt(0) == '#') {
      SyntaxTreeNode element = findStylesheet(root, _target.substring(1));
      if (element == null) {
        ErrorMsg msg = new ErrorMsg(ErrorMsg.MISSING_XSLT_TARGET_ERR,
            _target, root);
        throw new CompilerException(msg.toString());
      }
      return (element);
    } else {
      try {
        String path = _target;
        if (path.indexOf(":") == -1) {
          path = "file:" + path;
        }
        path = SystemIDResolver.getAbsoluteURI(path);
        String accessError = SecuritySupport.checkAccess(path,
            (String) _xsltc.getProperty(XMLConstants.ACCESS_EXTERNAL_STYLESHEET),
            XalanConstants.ACCESS_EXTERNAL_ALL);
        if (accessError != null) {
          ErrorMsg msg = new ErrorMsg(ErrorMsg.ACCESSING_XSLT_TARGET_ERR,
              SecuritySupport.sanitizePath(_target), accessError,
              root);
          throw new CompilerException(msg.toString());
        }
      } catch (IOException ex) {
        throw new CompilerException(ex);
      }

      return (loadExternalStylesheet(_target));
    }
  }

  /**
   * Find a Stylesheet element with a specific ID attribute value.
   * This method is used to find a Stylesheet node that is referred
   * in a <?xml-stylesheet ... ?> processing instruction.
   */
  private SyntaxTreeNode findStylesheet(SyntaxTreeNode root, String href) {

    if (root == null) {
      return null;
    }

    if (root instanceof Stylesheet) {
      String id = root.getAttribute("id");
      if (id.equals(href)) {
        return root;
      }
    }
    Vector children = root.getContents();
    if (children != null) {
      final int count = children.size();
      for (int i = 0; i < count; i++) {
        SyntaxTreeNode child = (SyntaxTreeNode) children.elementAt(i);
        SyntaxTreeNode node = findStylesheet(child, href);
        if (node != null) {
          return node;
        }
      }
    }
    return null;
  }

  /**
   * For embedded stylesheets: Load an external file with stylesheet
   */
  private SyntaxTreeNode loadExternalStylesheet(String location)
      throws CompilerException {

    InputSource source;

    // Check if the location is URL or a local file
    if ((new File(location)).exists()) {
      source = new InputSource("file:" + location);
    } else {
      source = new InputSource(location);
    }

    SyntaxTreeNode external = (SyntaxTreeNode) parse(source);
    return (external);
  }

  private void initAttrTable(String elementName, String[] attrs) {
    _instructionAttrs.put(getQName(XSLT_URI, XSL, elementName),
        attrs);
  }

  private void initInstructionAttrs() {
    initAttrTable("template",
        new String[]{"match", "name", "priority", "mode"});
    initAttrTable("stylesheet",
        new String[]{"id", "version", "extension-element-prefixes",
            "exclude-result-prefixes"});
    initAttrTable("transform",
        new String[]{"id", "version", "extension-element-prefixes",
            "exclude-result-prefixes"});
    initAttrTable("text", new String[]{"disable-output-escaping"});
    initAttrTable("if", new String[]{"test"});
    initAttrTable("choose", new String[]{});
    initAttrTable("when", new String[]{"test"});
    initAttrTable("otherwise", new String[]{});
    initAttrTable("for-each", new String[]{"select"});
    initAttrTable("message", new String[]{"terminate"});
    initAttrTable("number",
        new String[]{"level", "count", "from", "value", "format", "lang",
            "letter-value", "grouping-separator", "grouping-size"});
    initAttrTable("comment", new String[]{});
    initAttrTable("copy", new String[]{"use-attribute-sets"});
    initAttrTable("copy-of", new String[]{"select"});
    initAttrTable("param", new String[]{"name", "select"});
    initAttrTable("with-param", new String[]{"name", "select"});
    initAttrTable("variable", new String[]{"name", "select"});
    initAttrTable("output",
        new String[]{"method", "version", "encoding",
            "omit-xml-declaration", "standalone", "doctype-public",
            "doctype-system", "cdata-section-elements", "indent",
            "media-type"});
    initAttrTable("sort",
        new String[]{"select", "order", "case-order", "lang", "data-type"});
    initAttrTable("key", new String[]{"name", "match", "use"});
    initAttrTable("fallback", new String[]{});
    initAttrTable("attribute", new String[]{"name", "namespace"});
    initAttrTable("attribute-set",
        new String[]{"name", "use-attribute-sets"});
    initAttrTable("value-of",
        new String[]{"select", "disable-output-escaping"});
    initAttrTable("element",
        new String[]{"name", "namespace", "use-attribute-sets"});
    initAttrTable("call-template", new String[]{"name"});
    initAttrTable("apply-templates", new String[]{"select", "mode"});
    initAttrTable("apply-imports", new String[]{});
    initAttrTable("decimal-format",
        new String[]{"name", "decimal-separator", "grouping-separator",
            "infinity", "minus-sign", "NaN", "percent", "per-mille",
            "zero-digit", "digit", "pattern-separator"});
    initAttrTable("import", new String[]{"href"});
    initAttrTable("include", new String[]{"href"});
    initAttrTable("strip-space", new String[]{"elements"});
    initAttrTable("preserve-space", new String[]{"elements"});
    initAttrTable("processing-instruction", new String[]{"name"});
    initAttrTable("namespace-alias",
        new String[]{"stylesheet-prefix", "result-prefix"});
  }


  /**
   * Initialize the _instructionClasses Hashtable, which maps XSL element
   * names to Java classes in this package.
   */
  private void initStdClasses() {
    initStdClass("template", "Template");
    initStdClass("stylesheet", "Stylesheet");
    initStdClass("transform", "Stylesheet");
    initStdClass("text", "Text");
    initStdClass("if", "If");
    initStdClass("choose", "Choose");
    initStdClass("when", "When");
    initStdClass("otherwise", "Otherwise");
    initStdClass("for-each", "ForEach");
    initStdClass("message", "Message");
    initStdClass("number", "Number");
    initStdClass("comment", "Comment");
    initStdClass("copy", "Copy");
    initStdClass("copy-of", "CopyOf");
    initStdClass("param", "Param");
    initStdClass("with-param", "WithParam");
    initStdClass("variable", "Variable");
    initStdClass("output", "Output");
    initStdClass("sort", "Sort");
    initStdClass("key", "Key");
    initStdClass("fallback", "Fallback");
    initStdClass("attribute", "XslAttribute");
    initStdClass("attribute-set", "AttributeSet");
    initStdClass("value-of", "ValueOf");
    initStdClass("element", "XslElement");
    initStdClass("call-template", "CallTemplate");
    initStdClass("apply-templates", "ApplyTemplates");
    initStdClass("apply-imports", "ApplyImports");
    initStdClass("decimal-format", "DecimalFormatting");
    initStdClass("import", "Import");
    initStdClass("include", "Include");
    initStdClass("strip-space", "Whitespace");
    initStdClass("preserve-space", "Whitespace");
    initStdClass("processing-instruction", "ProcessingInstruction");
    initStdClass("namespace-alias", "NamespaceAlias");
  }

  private void initStdClass(String elementName, String className) {
    _instructionClasses.put(getQName(XSLT_URI, XSL, elementName),
        COMPILER_PACKAGE + '.' + className);
  }

  public boolean elementSupported(String namespace, String localName) {
    return (_instructionClasses.get(getQName(namespace, XSL, localName)) != null);
  }

  public boolean functionSupported(String fname) {
    return (_symbolTable.lookupPrimop(fname) != null);
  }

  private void initExtClasses() {
    initExtClass("output", "TransletOutput");
    initExtClass(REDIRECT_URI, "write", "TransletOutput");
  }

  private void initExtClass(String elementName, String className) {
    _instructionClasses.put(getQName(TRANSLET_URI, TRANSLET, elementName),
        COMPILER_PACKAGE + '.' + className);
  }

  private void initExtClass(String namespace, String elementName, String className) {
    _instructionClasses.put(getQName(namespace, TRANSLET, elementName),
        COMPILER_PACKAGE + '.' + className);
  }

  /**
   * Add primops and base functions to the symbol table.
   */
  private void initSymbolTable() {
    MethodType I_V = new MethodType(Type.Int, Type.Void);
    MethodType I_R = new MethodType(Type.Int, Type.Real);
    MethodType I_S = new MethodType(Type.Int, Type.String);
    MethodType I_D = new MethodType(Type.Int, Type.NodeSet);
    MethodType R_I = new MethodType(Type.Real, Type.Int);
    MethodType R_V = new MethodType(Type.Real, Type.Void);
    MethodType R_R = new MethodType(Type.Real, Type.Real);
    MethodType R_D = new MethodType(Type.Real, Type.NodeSet);
    MethodType R_O = new MethodType(Type.Real, Type.Reference);
    MethodType I_I = new MethodType(Type.Int, Type.Int);
    MethodType D_O = new MethodType(Type.NodeSet, Type.Reference);
    MethodType D_V = new MethodType(Type.NodeSet, Type.Void);
    MethodType D_S = new MethodType(Type.NodeSet, Type.String);
    MethodType D_D = new MethodType(Type.NodeSet, Type.NodeSet);
    MethodType A_V = new MethodType(Type.Node, Type.Void);
    MethodType S_V = new MethodType(Type.String, Type.Void);
    MethodType S_S = new MethodType(Type.String, Type.String);
    MethodType S_A = new MethodType(Type.String, Type.Node);
    MethodType S_D = new MethodType(Type.String, Type.NodeSet);
    MethodType S_O = new MethodType(Type.String, Type.Reference);
    MethodType B_O = new MethodType(Type.Boolean, Type.Reference);
    MethodType B_V = new MethodType(Type.Boolean, Type.Void);
    MethodType B_B = new MethodType(Type.Boolean, Type.Boolean);
    MethodType B_S = new MethodType(Type.Boolean, Type.String);
    MethodType D_X = new MethodType(Type.NodeSet, Type.Object);
    MethodType R_RR = new MethodType(Type.Real, Type.Real, Type.Real);
    MethodType I_II = new MethodType(Type.Int, Type.Int, Type.Int);
    MethodType B_RR = new MethodType(Type.Boolean, Type.Real, Type.Real);
    MethodType B_II = new MethodType(Type.Boolean, Type.Int, Type.Int);
    MethodType S_SS = new MethodType(Type.String, Type.String, Type.String);
    MethodType S_DS = new MethodType(Type.String, Type.Real, Type.String);
    MethodType S_SR = new MethodType(Type.String, Type.String, Type.Real);
    MethodType O_SO = new MethodType(Type.Reference, Type.String, Type.Reference);

    MethodType D_SS =
        new MethodType(Type.NodeSet, Type.String, Type.String);
    MethodType D_SD =
        new MethodType(Type.NodeSet, Type.String, Type.NodeSet);
    MethodType B_BB =
        new MethodType(Type.Boolean, Type.Boolean, Type.Boolean);
    MethodType B_SS =
        new MethodType(Type.Boolean, Type.String, Type.String);
    MethodType S_SD =
        new MethodType(Type.String, Type.String, Type.NodeSet);
    MethodType S_DSS =
        new MethodType(Type.String, Type.Real, Type.String, Type.String);
    MethodType S_SRR =
        new MethodType(Type.String, Type.String, Type.Real, Type.Real);
    MethodType S_SSS =
        new MethodType(Type.String, Type.String, Type.String, Type.String);

        /*
         * Standard functions: implemented but not in this table concat().
         * When adding a new function make sure to uncomment
         * the corresponding line in <tt>FunctionAvailableCall</tt>.
         */

    // The following functions are inlined

    _symbolTable.addPrimop("current", A_V);
    _symbolTable.addPrimop("last", I_V);
    _symbolTable.addPrimop("position", I_V);
    _symbolTable.addPrimop("true", B_V);
    _symbolTable.addPrimop("false", B_V);
    _symbolTable.addPrimop("not", B_B);
    _symbolTable.addPrimop("name", S_V);
    _symbolTable.addPrimop("name", S_A);
    _symbolTable.addPrimop("generate-id", S_V);
    _symbolTable.addPrimop("generate-id", S_A);
    _symbolTable.addPrimop("ceiling", R_R);
    _symbolTable.addPrimop("floor", R_R);
    _symbolTable.addPrimop("round", R_R);
    _symbolTable.addPrimop("contains", B_SS);
    _symbolTable.addPrimop("number", R_O);
    _symbolTable.addPrimop("number", R_V);
    _symbolTable.addPrimop("boolean", B_O);
    _symbolTable.addPrimop("string", S_O);
    _symbolTable.addPrimop("string", S_V);
    _symbolTable.addPrimop("translate", S_SSS);
    _symbolTable.addPrimop("string-length", I_V);
    _symbolTable.addPrimop("string-length", I_S);
    _symbolTable.addPrimop("starts-with", B_SS);
    _symbolTable.addPrimop("format-number", S_DS);
    _symbolTable.addPrimop("format-number", S_DSS);
    _symbolTable.addPrimop("unparsed-entity-uri", S_S);
    _symbolTable.addPrimop("key", D_SS);
    _symbolTable.addPrimop("key", D_SD);
    _symbolTable.addPrimop("id", D_S);
    _symbolTable.addPrimop("id", D_D);
    _symbolTable.addPrimop("namespace-uri", S_V);
    _symbolTable.addPrimop("function-available", B_S);
    _symbolTable.addPrimop("element-available", B_S);
    _symbolTable.addPrimop("document", D_S);
    _symbolTable.addPrimop("document", D_V);

    // The following functions are implemented in the basis library
    _symbolTable.addPrimop("count", I_D);
    _symbolTable.addPrimop("sum", R_D);
    _symbolTable.addPrimop("local-name", S_V);
    _symbolTable.addPrimop("local-name", S_D);
    _symbolTable.addPrimop("namespace-uri", S_V);
    _symbolTable.addPrimop("namespace-uri", S_D);
    _symbolTable.addPrimop("substring", S_SR);
    _symbolTable.addPrimop("substring", S_SRR);
    _symbolTable.addPrimop("substring-after", S_SS);
    _symbolTable.addPrimop("substring-before", S_SS);
    _symbolTable.addPrimop("normalize-space", S_V);
    _symbolTable.addPrimop("normalize-space", S_S);
    _symbolTable.addPrimop("system-property", S_S);

    // Extensions
    _symbolTable.addPrimop("nodeset", D_O);
    _symbolTable.addPrimop("objectType", S_O);
    _symbolTable.addPrimop("cast", O_SO);

    // Operators +, -, *, /, % defined on real types.
    _symbolTable.addPrimop("+", R_RR);
    _symbolTable.addPrimop("-", R_RR);
    _symbolTable.addPrimop("*", R_RR);
    _symbolTable.addPrimop("/", R_RR);
    _symbolTable.addPrimop("%", R_RR);

    // Operators +, -, * defined on integer types.
    // Operators / and % are not  defined on integers (may cause exception)
    _symbolTable.addPrimop("+", I_II);
    _symbolTable.addPrimop("-", I_II);
    _symbolTable.addPrimop("*", I_II);

    // Operators <, <= >, >= defined on real types.
    _symbolTable.addPrimop("<", B_RR);
    _symbolTable.addPrimop("<=", B_RR);
    _symbolTable.addPrimop(">", B_RR);
    _symbolTable.addPrimop(">=", B_RR);

    // Operators <, <= >, >= defined on int types.
    _symbolTable.addPrimop("<", B_II);
    _symbolTable.addPrimop("<=", B_II);
    _symbolTable.addPrimop(">", B_II);
    _symbolTable.addPrimop(">=", B_II);

    // Operators <, <= >, >= defined on boolean types.
    _symbolTable.addPrimop("<", B_BB);
    _symbolTable.addPrimop("<=", B_BB);
    _symbolTable.addPrimop(">", B_BB);
    _symbolTable.addPrimop(">=", B_BB);

    // Operators 'and' and 'or'.
    _symbolTable.addPrimop("or", B_BB);
    _symbolTable.addPrimop("and", B_BB);

    // Unary minus.
    _symbolTable.addPrimop("u-", R_R);
    _symbolTable.addPrimop("u-", I_I);
  }

  public SymbolTable getSymbolTable() {
    return _symbolTable;
  }

  public Template getTemplate() {
    return _template;
  }

  public void setTemplate(Template template) {
    _template = template;
  }

  private int _templateIndex = 0;

  public int getTemplateIndex() {
    return (_templateIndex++);
  }

  /**
   * Creates a new node in the abstract syntax tree. This node can be
   * o) a supported XSLT 1.0 element
   * o) an unsupported XSLT element (post 1.0)
   * o) a supported XSLT extension
   * o) an unsupported XSLT extension
   * o) a literal result element (not an XSLT element and not an extension)
   * Unsupported elements do not directly generate an error. We have to wait
   * until we have received all child elements of an unsupported element to
   * see if any <xsl:fallback> elements exist.
   */

  private boolean versionIsOne = true;

  public SyntaxTreeNode makeInstance(String uri, String prefix,
      String local, Attributes attributes) {
    SyntaxTreeNode node = null;
    QName qname = getQName(uri, prefix, local);
    String className = (String) _instructionClasses.get(qname);

    if (className != null) {
      try {
        final Class clazz = ObjectFactory.findProviderClass(className, true);
        node = (SyntaxTreeNode) clazz.newInstance();
        node.setQName(qname);
        node.setParser(this);
        if (_locator != null) {
          node.setLineNumber(getLineNumber());
        }
        if (node instanceof Stylesheet) {
          _xsltc.setStylesheet((Stylesheet) node);
        }
        checkForSuperfluousAttributes(node, attributes);
      } catch (ClassNotFoundException e) {
        ErrorMsg err = new ErrorMsg(ErrorMsg.CLASS_NOT_FOUND_ERR, node);
        reportError(ERROR, err);
      } catch (Exception e) {
        ErrorMsg err = new ErrorMsg(ErrorMsg.INTERNAL_ERR,
            e.getMessage(), node);
        reportError(FATAL, err);
      }
    } else {
      if (uri != null) {
        // Check if the element belongs in our namespace
        if (uri.equals(XSLT_URI)) {
          node = new UnsupportedElement(uri, prefix, local, false);
          UnsupportedElement element = (UnsupportedElement) node;
          ErrorMsg msg = new ErrorMsg(ErrorMsg.UNSUPPORTED_XSL_ERR,
              getLineNumber(), local);
          element.setErrorMessage(msg);
          if (versionIsOne) {
            reportError(UNSUPPORTED, msg);
          }
        }
        // Check if this is an XSLTC extension element
        else if (uri.equals(TRANSLET_URI)) {
          node = new UnsupportedElement(uri, prefix, local, true);
          UnsupportedElement element = (UnsupportedElement) node;
          ErrorMsg msg = new ErrorMsg(ErrorMsg.UNSUPPORTED_EXT_ERR,
              getLineNumber(), local);
          element.setErrorMessage(msg);
        }
        // Check if this is an extension of some other XSLT processor
        else {
          Stylesheet sheet = _xsltc.getStylesheet();
          if ((sheet != null) && (sheet.isExtension(uri))) {
            if (sheet != (SyntaxTreeNode) _parentStack.peek()) {
              node = new UnsupportedElement(uri, prefix, local, true);
              UnsupportedElement elem = (UnsupportedElement) node;
              ErrorMsg msg =
                  new ErrorMsg(ErrorMsg.UNSUPPORTED_EXT_ERR,
                      getLineNumber(),
                      prefix + ":" + local);
              elem.setErrorMessage(msg);
            }
          }
        }
      }
      if (node == null) {
        node = new LiteralElement();
        node.setLineNumber(getLineNumber());
      }
    }
    if ((node != null) && (node instanceof LiteralElement)) {
      ((LiteralElement) node).setQName(qname);
    }
    return (node);
  }

  /**
   * checks the list of attributes against a list of allowed attributes
   * for a particular element node.
   */
  private void checkForSuperfluousAttributes(SyntaxTreeNode node,
      Attributes attrs) {
    QName qname = node.getQName();
    boolean isStylesheet = (node instanceof Stylesheet);
    String[] legal = (String[]) _instructionAttrs.get(qname);
    if (versionIsOne && legal != null) {
      int j;
      final int n = attrs.getLength();

      for (int i = 0; i < n; i++) {
        final String attrQName = attrs.getQName(i);

        if (isStylesheet && attrQName.equals("version")) {
          versionIsOne = attrs.getValue(i).equals("1.0");
        }

        // Ignore if special or if it has a prefix
        if (attrQName.startsWith("xml") ||
            attrQName.indexOf(':') > 0) {
          continue;
        }

        for (j = 0; j < legal.length; j++) {
          if (attrQName.equalsIgnoreCase(legal[j])) {
            break;
          }
        }
        if (j == legal.length) {
          final ErrorMsg err =
              new ErrorMsg(ErrorMsg.ILLEGAL_ATTRIBUTE_ERR,
                  attrQName, node);
          // Workaround for the TCK failure ErrorListener.errorTests.error001..
          err.setWarningError(true);
          reportError(WARNING, err);
        }
      }
    }
  }


  /**
   * Parse an XPath expression:
   *
   * @param parent - XSL element where the expression occured
   * @param exp - textual representation of the expression
   */
  public Expression parseExpression(SyntaxTreeNode parent, String exp) {
    return (Expression) parseTopLevel(parent, "<EXPRESSION>" + exp, null);
  }

  /**
   * Parse an XPath expression:
   *
   * @param parent - XSL element where the expression occured
   * @param attr - name of this element's attribute to get expression from
   * @param def - default expression (if the attribute was not found)
   */
  public Expression parseExpression(SyntaxTreeNode parent,
      String attr, String def) {
    // Get the textual representation of the expression (if any)
    String exp = parent.getAttribute(attr);
    // Use the default expression if none was found
    if ((exp.length() == 0) && (def != null)) {
      exp = def;
    }
    // Invoke the XPath parser
    return (Expression) parseTopLevel(parent, "<EXPRESSION>" + exp, exp);
  }

  /**
   * Parse an XPath pattern:
   *
   * @param parent - XSL element where the pattern occured
   * @param pattern - textual representation of the pattern
   */
  public Pattern parsePattern(SyntaxTreeNode parent, String pattern) {
    return (Pattern) parseTopLevel(parent, "<PATTERN>" + pattern, pattern);
  }

  /**
   * Parse an XPath pattern:
   *
   * @param parent - XSL element where the pattern occured
   * @param attr - name of this element's attribute to get pattern from
   * @param def - default pattern (if the attribute was not found)
   */
  public Pattern parsePattern(SyntaxTreeNode parent,
      String attr, String def) {
    // Get the textual representation of the pattern (if any)
    String pattern = parent.getAttribute(attr);
    // Use the default pattern if none was found
    if ((pattern.length() == 0) && (def != null)) {
      pattern = def;
    }
    // Invoke the XPath parser
    return (Pattern) parseTopLevel(parent, "<PATTERN>" + pattern, pattern);
  }

  /**
   * Parse an XPath expression or pattern using the generated XPathParser
   * The method will return a Dummy node if the XPath parser fails.
   */
  private SyntaxTreeNode parseTopLevel(SyntaxTreeNode parent, String text,
      String expression) {
    int line = getLineNumber();

    try {
      _xpathParser.setScanner(new XPathLexer(new StringReader(text)));
      Symbol result = _xpathParser.parse(expression, line);
      if (result != null) {
        final SyntaxTreeNode node = (SyntaxTreeNode) result.value;
        if (node != null) {
          node.setParser(this);
          node.setParent(parent);
          node.setLineNumber(line);
// System.out.println("e = " + text + " " + node);
          return node;
        }
      }
      reportError(ERROR, new ErrorMsg(ErrorMsg.XPATH_PARSER_ERR,
          expression, parent));
    } catch (Exception e) {
      if (_xsltc.debug()) {
        e.printStackTrace();
      }
      reportError(ERROR, new ErrorMsg(ErrorMsg.XPATH_PARSER_ERR,
          expression, parent));
    }

    // Return a dummy pattern (which is an expression)
    SyntaxTreeNode.Dummy.setParser(this);
    return SyntaxTreeNode.Dummy;
  }

  /************************ ERROR HANDLING SECTION ************************/

  /**
   * Returns true if there were any errors during compilation
   */
  public boolean errorsFound() {
    return _errors.size() > 0;
  }

  /**
   * Prints all compile-time errors
   */
  public void printErrors() {
    final int size = _errors.size();
    if (size > 0) {
      System.err.println(new ErrorMsg(ErrorMsg.COMPILER_ERROR_KEY));
      for (int i = 0; i < size; i++) {
        System.err.println("  " + _errors.elementAt(i));
      }
    }
  }

  /**
   * Prints all compile-time warnings
   */
  public void printWarnings() {
    final int size = _warnings.size();
    if (size > 0) {
      System.err.println(new ErrorMsg(ErrorMsg.COMPILER_WARNING_KEY));
      for (int i = 0; i < size; i++) {
        System.err.println("  " + _warnings.elementAt(i));
      }
    }
  }

  /**
   * Common error/warning message handler
   */
  public void reportError(final int category, final ErrorMsg error) {
    switch (category) {
      case Constants.INTERNAL:
        // Unexpected internal errors, such as null-ptr exceptions, etc.
        // Immediately terminates compilation, no translet produced
        _errors.addElement(error);
        break;
      case Constants.UNSUPPORTED:
        // XSLT elements that are not implemented and unsupported ext.
        // Immediately terminates compilation, no translet produced
        _errors.addElement(error);
        break;
      case Constants.FATAL:
        // Fatal error in the stylesheet input (parsing or content)
        // Immediately terminates compilation, no translet produced
        _errors.addElement(error);
        break;
      case Constants.ERROR:
        // Other error in the stylesheet input (parsing or content)
        // Does not terminate compilation, no translet produced
        _errors.addElement(error);
        break;
      case Constants.WARNING:
        // Other error in the stylesheet input (content errors only)
        // Does not terminate compilation, a translet is produced
        _warnings.addElement(error);
        break;
    }
  }

  public Vector getErrors() {
    return _errors;
  }

  public Vector getWarnings() {
    return _warnings;
  }

  /************************ SAX2 ContentHandler INTERFACE *****************/

  private Stack _parentStack = null;
  private Hashtable _prefixMapping = null;

  /**
   * SAX2: Receive notification of the beginning of a document.
   */
  public void startDocument() {
    _root = null;
    _target = null;
    _prefixMapping = null;
    _parentStack = new Stack();
  }

  /**
   * SAX2: Receive notification of the end of a document.
   */
  public void endDocument() {
  }


  /**
   * SAX2: Begin the scope of a prefix-URI Namespace mapping.
   * This has to be passed on to the symbol table!
   */
  public void startPrefixMapping(String prefix, String uri) {
    if (_prefixMapping == null) {
      _prefixMapping = new Hashtable();
    }
    _prefixMapping.put(prefix, uri);
  }

  /**
   * SAX2: End the scope of a prefix-URI Namespace mapping.
   * This has to be passed on to the symbol table!
   */
  public void endPrefixMapping(String prefix) {
  }

  /**
   * SAX2: Receive notification of the beginning of an element.
   * The parser may re-use the attribute list that we're passed so
   * we clone the attributes in our own Attributes implementation
   */
  public void startElement(String uri, String localname,
      String qname, Attributes attributes)
      throws SAXException {
    final int col = qname.lastIndexOf(':');
    final String prefix = (col == -1) ? null : qname.substring(0, col);

    SyntaxTreeNode element = makeInstance(uri, prefix,
        localname, attributes);
    if (element == null) {
      ErrorMsg err = new ErrorMsg(ErrorMsg.ELEMENT_PARSE_ERR,
          prefix + ':' + localname);
      throw new SAXException(err.toString());
    }

    // If this is the root element of the XML document we need to make sure
    // that it contains a definition of the XSL namespace URI
    if (_root == null) {
      if ((_prefixMapping == null) ||
          (_prefixMapping.containsValue(Constants.XSLT_URI) == false)) {
        _rootNamespaceDef = false;
      } else {
        _rootNamespaceDef = true;
      }
      _root = element;
    } else {
      SyntaxTreeNode parent = (SyntaxTreeNode) _parentStack.peek();
      parent.addElement(element);
      element.setParent(parent);
    }
    element.setAttributes(new AttributesImpl(attributes));
    element.setPrefixMapping(_prefixMapping);

    if (element instanceof Stylesheet) {
      // Extension elements and excluded elements have to be
      // handled at this point in order to correctly generate
      // Fallback elements from <xsl:fallback>s.
      getSymbolTable().setCurrentNode(element);
      ((Stylesheet) element).declareExtensionPrefixes(this);
    }

    _prefixMapping = null;
    _parentStack.push(element);
  }

  /**
   * SAX2: Receive notification of the end of an element.
   */
  public void endElement(String uri, String localname, String qname) {
    _parentStack.pop();
  }

  /**
   * SAX2: Receive notification of character data.
   */
  public void characters(char[] ch, int start, int length) {
    String string = new String(ch, start, length);
    SyntaxTreeNode parent = (SyntaxTreeNode) _parentStack.peek();

    if (string.length() == 0) {
      return;
    }

    // If this text occurs within an <xsl:text> element we append it
    // as-is to the existing text element
    if (parent instanceof Text) {
      ((Text) parent).setText(string);
      return;
    }

    // Ignore text nodes that occur directly under <xsl:stylesheet>
    if (parent instanceof Stylesheet) {
      return;
    }

    SyntaxTreeNode bro = parent.lastChild();
    if ((bro != null) && (bro instanceof Text)) {
      Text text = (Text) bro;
      if (!text.isTextElement()) {
        if ((length > 1) || (((int) ch[0]) < 0x100)) {
          text.setText(string);
          return;
        }
      }
    }

    // Add it as a regular text node otherwise
    parent.addElement(new Text(string));
  }

  private String getTokenValue(String token) {
    final int start = token.indexOf('"');
    final int stop = token.lastIndexOf('"');
    return token.substring(start + 1, stop);
  }

  /**
   * SAX2: Receive notification of a processing instruction.
   * These require special handling for stylesheet PIs.
   */
  public void processingInstruction(String name, String value) {
    // We only handle the <?xml-stylesheet ...?> PI
    if ((_target == null) && (name.equals("xml-stylesheet"))) {

      String href = null;    // URI of stylesheet found
      String media = null;   // Media of stylesheet found
      String title = null;   // Title of stylesheet found
      String charset = null; // Charset of stylesheet found

      // Get the attributes from the processing instruction
      StringTokenizer tokens = new StringTokenizer(value);
      while (tokens.hasMoreElements()) {
        String token = (String) tokens.nextElement();
        if (token.startsWith("href")) {
          href = getTokenValue(token);
        } else if (token.startsWith("media")) {
          media = getTokenValue(token);
        } else if (token.startsWith("title")) {
          title = getTokenValue(token);
        } else if (token.startsWith("charset")) {
          charset = getTokenValue(token);
        }
      }

      // Set the target to this PI's href if the parameters are
      // null or match the corresponding attributes of this PI.
      if (((_PImedia == null) || (_PImedia.equals(media))) &&
          ((_PItitle == null) || (_PImedia.equals(title))) &&
          ((_PIcharset == null) || (_PImedia.equals(charset)))) {
        _target = href;
      }
    }
  }

  /**
   * IGNORED - all ignorable whitespace is ignored
   */
  public void ignorableWhitespace(char[] ch, int start, int length) {
  }

  /**
   * IGNORED - we do not have to do anything with skipped entities
   */
  public void skippedEntity(String name) {
  }

  /**
   * Store the document locator to later retrieve line numbers of all
   * elements from the stylesheet
   */
  public void setDocumentLocator(Locator locator) {
    _locator = locator;
  }

  /**
   * Get the line number, or zero
   * if there is no _locator.
   */
  private int getLineNumber() {
    int line = 0;
    if (_locator != null) {
      line = _locator.getLineNumber();
    }
    return line;
  }

}
